package com.alex.monitor.core.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.StopWatch;
import com.alex.monitor.core.alarm.MonitorAlarm;
import com.alex.monitor.core.alarm.impl.DefaultEmailMonitorAlarm;
import com.alex.monitor.core.alarm.impl.DefaultHttpMonitorAlarm;
import com.alex.monitor.core.alarm.impl.DefaultLogMonitorAlarm;
import com.alex.monitor.core.annotation.MethodMonitor;
import com.alex.monitor.core.annotation.TimeMethodMonitor;
import com.alex.monitor.core.enums.AlarmType;
import com.alex.monitor.core.enums.TriggerType;
import com.alex.monitor.core.properties.AlexMonitorProperties;
import com.alex.monitor.core.rule.MonitorRule;
import com.google.common.collect.Lists;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.task.TaskExecutor;

/**
 * 方法AOP配置
 *
 * @author Alex.Liu
 * @date: 2025-02-16 14:29
 * @since 1.0.0
 */
@Aspect
public class AlexMethodMonitorAspect implements InitializingBean, ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(AlexMethodMonitorAspect.class);

    private final TaskExecutor executor;

    private final AlexMonitorProperties alexMonitorProperties;

    /**
     * Spring上下文
     */
    private static ApplicationContext applicationContext;


    public AlexMethodMonitorAspect(TaskExecutor executor, AlexMonitorProperties alexMonitorProperties) {
        this.executor = executor;
        this.alexMonitorProperties = alexMonitorProperties;
    }

    /**
     * 监控规则缓存
     */
    private static final Map<Class<? extends MonitorRule>, MonitorRule> RULE_CACHE = new HashMap<>();

    /**
     * 发送器缓存
     */
    private static final Map<Class<? extends MonitorAlarm>, MonitorAlarm> SENDER_CACHE = new HashMap<>();

    @Pointcut("@annotation(com.alex.monitor.core.annotation.MethodMonitor)")
    public void methodMonitor() {
    }

    /**
     * 监控方法耗时
     */
    @Pointcut("@annotation(com.alex.monitor.core.annotation.TimeMethodMonitor)")
    public void timeMethodMonitor() {
    }


    /**
     * 发送通知
     * <p>
     * 根据方法签名、执行结果和异常情况,判断是否需要发送监控通知。
     * 如果需要发送,则异步执行发送逻辑。
     *
     * @param signature 方法签名,包含被监控方法的相关信息
     * @param result    方法执行的返回结果,可能为null
     * @param exception 方法执行过程中抛出的异常,可能为null
     */
    protected void sendIfNecessary(final MethodSignature signature, Object result, final Throwable exception) {
        executor.execute(() -> {
            log.debug("MethodMonitorAspect.methodMonitor(): {}", signature);
            Method method = signature.getMethod();
            MethodMonitor annotation = method.getAnnotation(MethodMonitor.class);
            MonitorRule rule = RULE_CACHE.computeIfAbsent(annotation.rule(), r -> {
                try {
                    return r.newInstance();
                } catch (Exception e) {
                    log.error("alex annotation: get rule instance error class {}", r.getName(), e);
                    throw new RuntimeException(e);
                }
            });
            List<MonitorAlarm> senders = Lists.newArrayList();
            if (CollUtil.isEmpty(alexMonitorProperties.getAlarms())) {
                List<AlarmType> types = alexMonitorProperties.getAlarmTypes();
                if (CollUtil.isEmpty(types)) {
                    throw new RuntimeException("alex annotation: get MonitorSender instance error class");
                }
                for (AlarmType type : types) {
                    MonitorAlarm sender;
                    switch (type) {
                        case LOG:
                            sender = SENDER_CACHE.get(DefaultLogMonitorAlarm.class);
                            break;
                        case EMAIL:
                            sender = SENDER_CACHE.get(DefaultEmailMonitorAlarm.class);
                            break;
                        case HTTP:
                            sender = SENDER_CACHE.get(DefaultHttpMonitorAlarm.class);
                            break;
                        default:
                            throw new RuntimeException("alex annotation: get MonitorSender instance error type=" + type.name());
                    }
                    senders.add(sender);
                }
            } else {
                for (Class<MonitorAlarm> senderClazz : alexMonitorProperties.getAlarms()) {
                    MonitorAlarm sender = SENDER_CACHE.get(senderClazz);
                    senders.add(sender);
                }
            }
            for (TriggerType trigger : annotation.trigger()) {
                if (TriggerType.RESULT.equals(trigger) && rule.match(method, annotation, result)) {
                    senders.forEach(se -> se.alarmObj(method, annotation, result));
                }

                if (TriggerType.EXCEPTION.equals(trigger) && rule.match(method, annotation, exception)) {
                    senders.forEach(se -> se.alarmEx(method, annotation, exception));
                }
            }
        });
    }

    /**
     * 发送通知
     *
     * @param signature 方法签名
     * @param timeCost  方法耗时
     */
    protected void sendTimeIfNecessary(final MethodSignature signature, long timeCost) {
        executor.execute(() -> {
            Method method = signature.getMethod();
            TimeMethodMonitor annotation = method.getAnnotation(TimeMethodMonitor.class);
            if (timeCost > annotation.value()) {
                List<MonitorAlarm> alarms = Lists.newArrayList();
                if (CollUtil.isEmpty(alexMonitorProperties.getAlarms())) {
                    List<AlarmType> types = alexMonitorProperties.getAlarmTypes();
                    if (CollUtil.isEmpty(types)) {
                        throw new RuntimeException("alex annotation: get MonitorSender instance error class");
                    }
                    for (AlarmType type : types) {
                        MonitorAlarm sender;
                        switch (type) {
                            case LOG:
                                sender = SENDER_CACHE.get(DefaultLogMonitorAlarm.class);
                                break;
                            case EMAIL:
                                sender = SENDER_CACHE.get(DefaultEmailMonitorAlarm.class);
                                break;
                            case HTTP:
                                sender = SENDER_CACHE.get(DefaultHttpMonitorAlarm.class);
                                break;
                            default:
                                throw new RuntimeException("alex annotation: get MonitorSender instance error type=" + type.name());
                        }
                        alarms.add(sender);
                    }
                } else {
                    for (Class<MonitorAlarm> senderClazz : alexMonitorProperties.getAlarms()) {
                        MonitorAlarm sender = SENDER_CACHE.get(senderClazz);
                        alarms.add(sender);
                    }
                }
                alarms.forEach(alarm -> alarm.alarmTimeout(method, annotation, timeCost));
            }
        });
    }

    /**
     * 返回通知，在目标方法正常返回结果后执行
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(pointcut = "methodMonitor()", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        if (joinPoint.getSignature() instanceof MethodSignature) {
            this.sendIfNecessary((MethodSignature) joinPoint.getSignature(), result, null);
        }
    }

    /*
     * 该方法为环绕通知，用于监控方法执行时间
     */
    @Around("timeMethodMonitor()")
    public Object timeCostAroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 创建一个StopWatch实例，用于计算方法执行时间
        StopWatch watch = new StopWatch();

        // 获取被拦截的方法的签名，以便于后续的时间记录
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        try {
            // 开始计时
            watch.start();
            // 执行被拦截的方法，并返回结果
            return pjp.proceed();
        } finally {
            // 停止计时
            watch.stop();

            // 如果需要，发送该方法的执行时间
            this.sendTimeIfNecessary(signature, watch.getTotalTimeMillis());
        }
    }


    /**
     * 异常通知，当目标方法抛出异常时执行
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(pointcut = "methodMonitor()", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable ex) {
        if (joinPoint.getSignature() instanceof MethodSignature) {
            this.sendIfNecessary((MethodSignature) joinPoint.getSignature(), null, ex);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化到报警器缓存
        applicationContext.getBeansOfType(MonitorAlarm.class).forEach((k, v) -> SENDER_CACHE.put(v.getClass(), v));
        if (log.isDebugEnabled()) {
            log.debug("报警器初始化完成 SENDER_CACHE {}", SENDER_CACHE);
        }
    }

    @Override
    public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AlexMethodMonitorAspect.applicationContext = applicationContext;
    }
}